package core;

import core.data.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

public class Data<T> {
    public static final String VERSION = "3.0.0";
    public final DataCollector<T> data = new DataCollector<>();

    private final Map<String, DataHandle<?, ?>> library = new LinkedHashMap<>();
    private final Map<String, DataHandle<?, ?>> handles = new LinkedHashMap<>();
    private final Map<String, DataCollector<?>> global = new HashMap<>();
    private String handle = null;
    private STATUS status = STATUS.WAITING;
    private boolean pause = false;
    private Integer dataCode = null;
    private HandleEvent<Data<T>> finish = null;

    private final Queue queue = new Queue();
    private final DataError dataError = new DataError();

    private final EventActions<Data<T>> eventActions = new EventActions<>();

    public static Data<?> create() {
        return new Data<>();
    }

    public static <N> Data<N> create(N data){
        return new Data<N>().setData(data);
    }

    private void doEvent(Events onEvent){
        boolean pass = true;
        if(pause){
            pass = false;
            if(eventActions.hasListen()){
                HandleListen<Data<T>> action = eventActions.getListen();
                pass = action.apply(this);
            }
        }
        if(pass){
            HandleEvent<Data<T>> action = null;
            switch (onEvent){
                case SUCCESS -> {
                    if(this.eventActions.hasSuccess())
                        action = this.eventActions.getSuccess();
                }
                case FAILURE -> {
                    if(this.eventActions.hasFailure())
                        action = this.eventActions.getFailure();
                }
                case FINISH -> {
                    if(this.eventActions.hasFinish())
                        action = this.eventActions.getFinish();
                }
            }
            if(action != null) action.apply(this);
        }
    }

    public boolean getStatus(){
        return getStatus(STATUS.SUCCESS);
    }

    public boolean getStatus(STATUS status){
        return this.status == status;
    }

    public Data<T> setStatus(STATUS status){
        this.status = status;
        return this;
    }

    public boolean isHandle(String handle){
        return this.handle.equals(handle);
    }
    public boolean hasHandle(String handle){
        return library.containsKey(handle) || handles.containsKey(handle);
    }

    public Data<T> getRoot(){
        return this;
    }

    public Data<T> getHandle(){
        return this;
    }
    @SuppressWarnings("unchecked")
    public <P, D> DataHandle<P, D> getHandle(String handle){
        if(library.containsKey(handle)){
            return (DataHandle<P, D>) library.get(handle);
        }else if(handles.containsKey(handle)){
            return (DataHandle<P, D>) handles.get(handle);
        }
        return null;
    }
    public <J> DataHandle<T, J> setHandle(String handle){
        DataHandle<T, J> item = new DataHandle<>(handle, this);
        item.setParams(getData());
        return setHandle(item);
    }
    public <J> DataHandle<T, J> setHandle(String handle, HandleTest<T, J> test){
        DataHandle<T, J> item = new DataHandle<>(handle, this);
        item.setParams(getData()).data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return setHandle(item);
    }

    public <J, K> DataHandle<J, K> setHandle(J param, String handle){
        DataHandle<J, K> item = new DataHandle<>(handle, this);
        item.setParams(param);
        return setHandle(item);
    }

    public <J, K> DataHandle<J, K> setHandle(J param, String handle, HandleTest<J, K> test){
        DataHandle<J, K> item = new DataHandle<>(handle, this);
        item.setParams(param).data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return setHandle(item);
    }

    public <J, K> DataHandle<J, K> setHandle(DataHandle<J, K> handle){
        this.handles.put(handle.handle, handle);
        this.handle = handle.handle;
        pause = true;
        return handle;
    }

    public <J> DataHandle<T, J> addHandle(String handle, HandleProcess<T, J> process){
        DataHandle<T, J> item = new DataHandle<>(handle, this);
        return addHandle(item, () -> {
            item.setParams(getData());
            process.apply(item);
        });
    }
    public <J> DataHandle<T, J> addHandle(String handle, HandleProcess<T, J> process, HandleTest<T, J> test){
        DataHandle<T, J> item = new DataHandle<>(handle, this);
        item.data.onTest(() -> {
            if(test.apply(item)) item.success();
        });
        return addHandle(item, () -> {
            item.setParams(getData());
            process.apply(item);
        });

    }
    public <J, K> DataHandle<J, K> addHandle(DataHandle<J, K> handle, Runnable process){
        if(queue.isDone()) queue.init();
        queue.add(handle.handle, $ -> {
            if(status != STATUS.FAILURE){
                library.put(handle.handle, handle);
                this.handle = handle.handle;
                process.run();
                return false;
            }
            return true;
        });
        return handle;
    }

    public boolean hasGlobal(String key){
        return global.containsKey(key);
    }
    @SuppressWarnings("unchecked")
    public <D> D getGlobal(String key){
        if(global.containsKey(key)) {
            return (D) global.get(key).getValue();
        }
        return null;
    }

    public Data<T> setGlobal(String key, Object value){
        global.put(key, new DataCollector<>(value));
        return this;
    }

    public Data<T> publish(){
        global.put("root", data);
        return this;
    }

    public Data<T> publish(String handle){
        DataHandle<?, ?> item = getHandle(handle);
        if(item != null){
            global.put(handle, item.data);
        }
        return this;
    }

    public boolean getPaused(){
        return pause;
    }

    public Data<T> setPaused(boolean pause){
        this.pause = pause;
        return this;
    }

    public Data<T> block(HandleEvent<Data<T>> action){
        boolean paused = true;
        if(pause){
            paused = false;
        }else{
            pause = true;
        }
        action.apply(this);
        if(paused) pause = false;
        return this;
    }

    public Data<T> block(HandleEvent<Data<T>> action, HandleListen<Data<T>> listen){
        this.eventActions.setListen(listen);
        return block(action);
    }

    public Data<T> onSuccess(HandleEvent<Data<T>> action) {
        this.eventActions.setSuccess(action);
        return this;
    }

    public Data<T> onFailure(HandleEvent<Data<T>> action) {
        this.eventActions.setFailure(action);
        return this;
    }

    public Data<T> onFinish(HandleEvent<Data<T>> action) {
        this.eventActions.setFinish(action);
        return this;
    }

    public void success(){
        setStatus(STATUS.SUCCESS);
        doEvent(Events.SUCCESS);
        doEvent(Events.FINISH);
        next();
    }
    public void success(T value){
        setData(value);
        success();
    }

    public void failure(){
        setStatus(STATUS.FAILURE);
        doEvent(Events.FAILURE);
        doEvent(Events.FINISH);
        next();
    }

    public void failure(String message){
        dataError.setMessage(message);
        failure();
    }

    public void failure(Map<String, String> messages){
        dataError.setMessage(messages);
        failure();

    }

    public void failure(String key, String message){
        dataError.setMessage(key, message);
        failure();
    }

    public T getData(){
        return data.getValue();
    }

    public Data<T> setData(T value){
        data.setValue(value);
        return this;
    }

    public DataError getError(){
        return dataError;
    }

    public String getMessage(){
        return dataError.getMessage();
    }

    public Map<String, String> getMessages(){
        return dataError.getMessages();
    }

    public Data<T> setMessage(String message){
        dataError.setMessage(message);
        return this;
    }

    public Data<T> setMessage(Map<String, String> messages){
        dataError.setMessage(messages);
        return this;
    }

    public Data<T> setMessage(String key, String message){
        dataError.setMessage(key, message);
        return this;
    }

    public Integer getCode() {
        return dataCode;
    }

    public Data<T> setCode(int code){
        dataCode = code;
        return this;
    }

    public DataResult getResult(){
        DataResult result;
        if(getStatus(STATUS.SUCCESS)){
            result = new SuccessResult(data.getValue(), dataCode);
        }else{
            result = new FailureResult(dataError.getMessages(), dataCode);
        }
        return result;
    }

    public void next(){
        if(!pause) queue.next();
        if(queue.isDone() && finish != null)
            finish.apply(this);
    }

    public Data<T> start(HandleEvent<Data<T>> action){
        finish = action;
        return start();
    }

    public Data<T> start(){
        if(!library.isEmpty()){
            String lastHandle = queue.getCurrent();
            if(!handle.equals(lastHandle) && pause){
                pause = false;
                handle = lastHandle;
                next();
            }
        }else if(queue.isInit()){
            next();
        }
        return this;
    }
}
